import java.util.Arrays;
import java.util.Collections;
import java.util.Vector;

import org.joda.time.DateTime;
import org.joda.time.LocalDate;

//@author A0099943J
public class SchedulerLogic {
	private static final String  STATUS_ADD = "\"%1$s\" added to Scheduler.";
	private static final String  STATUS_DELETE = "%1$s deleted from Scheduler";
	private static final String  STATUS_COMPLETION = "%1$s marked as %2$s.";
	private static final String  STATUS_DONE = "completed";
	private static final String  STATUS_UNDONE = "incompleted";
	private static final String  STATUS_UPDATE = "Task %1$s is updated.";
	private static final String  STATUS_CLEAR = "All tasks cleared from Scheduler";
	private static final String  STATUS_UNDO = "The most recent change is reverted";
	private static final String  STATUS_SEARCH = "Search results";
	private static final String  NO_ARGUMENTS = "No arguments found.";
	private static final String  INVALID_COMMAND = "Invalid command.";
	private static final String  INDEX_OUT_OF_BOUNDS = "Index out of bounds.";
	private static final String  EMPTY_LOG = "Nothing to undo";
	private static final String  UNABLE_UNDO = "Unable to undo";
	private static final String  EXPIRED_TASK = "Task already expired";
	private static final String  DUPLICATE_TASK = "Task already exists in Scheduler.";

	private static final String  DATA_FILE = "data.xls";
	private static final String  LOG_FILE = "log.xls";

	private static final boolean DISABLE_LOG = false;

	private static final int	 NO_EDIT_INDEX = -1;
	private static final int	 EMPTY_INDEX_LIST = 0;
	private static final int	 COMPLETED  = 0;
	private static final int	 OUTSTANDING = 1;
	private static final int	 SEARCH = 2;
	private static final int	 RECENT = 3;
	private static final int	 EXPIRED = 4;

	private Storage storage;
	private TaskList list;
	private Vector<LogEntry> log;
	private Vector<Integer> recentChanges;
	private Vector<Integer> searchResults;

	//@author A0099943J
	public SchedulerLogic() {
		this(DATA_FILE,LOG_FILE);
	}

	//@author A0099943J
	public SchedulerLogic(String dataPath, String logPath) {
		storage			= new Storage(DATA_FILE, LOG_FILE);
		list 			= new TaskList(storage.loadDataFromDisk());
		log 			= storage.loadLogFromDisk();
		recentChanges 	= new Vector<Integer>();
	}

	//@author A0099943J
	public Vector <Integer> getDisplayIndex(int displayMode) {
		switch (displayMode) {
		case COMPLETED:
			return list.getCompleteTask();
		case OUTSTANDING:
			return list.getIncompleteTask();
		case SEARCH:
			return searchResults;
		case RECENT:
			return recentChanges;
		case EXPIRED:
			return list.getExpiredTasks();
		default:
			return getAllIndex();
		}
	}

	//@author A0099943J
	private Vector<Integer> getAllIndex() {
		Vector<Integer> allIndex = new Vector<Integer>();
		for (int i=0; i<list.getNumTask(); i++) {
			allIndex.add(i+1);
		}
		return allIndex;
	}

	//@author A0099943J
	public Vector<Task> getTaskList() {
		return list.toVector();
	}

	//@author A0099943J
	public String executeCommand(Command newCommand) {
		CommandType type = newCommand.getCmdType();
		switch (type) {
		case ADD: 
			return addTask(newCommand);
		case DELETE: 
			return deleteTask(newCommand);
		case EDIT:
			return editTask(newCommand);
		case MARK:
			return markTask(newCommand);
		case UNMARK:
			return unmarkTask(newCommand);
		case SEARCH:
			return searchTask(newCommand);
		case SHOW:
			return showTask(newCommand);
		case CLEAR:
			return clearTask(newCommand);
		case UNDO:
			return undoTask();
		default:
			return INVALID_COMMAND;
		}
	}

	//@author A0099943J
	private String searchTask(Command newCommand) {
		recentChanges.clear();
		Vector <String> keywords = newCommand.getSearchKeywords();
		searchResults = list.searchTaskByKeywords(keywords);
		return STATUS_SEARCH;
	}

	//@author A0099943J
	private String showTask(Command newCommand) {
		DateTime taskDateTime    = newCommand.getShowDateTime();
		boolean  searchOnlyDate  = newCommand.searchOnlyDate();

		if (taskDateTime == null) {
			return INVALID_COMMAND;
		}
		else if (searchOnlyDate) {
			LocalDate taskDate  = taskDateTime.toLocalDate();
			searchResults 		= list.getTaskOfDate(taskDate);
		}
		else {
			searchResults 		= list.getTaskOfDateTime(taskDateTime);
		}

		return STATUS_SEARCH;
	}

	//@author A0099943J
	private String undoTask() {
		if (log.isEmpty()) {
			return EMPTY_LOG;
		}
		else {
			LogEntry mostRecent = log.lastElement();
			log.remove(log.size()-1);
			CommandType logCmdType = mostRecent.getCmdType();
			switch (logCmdType) {
			case ADD:
				undoAdd(mostRecent);
				break;
			case DELETE:
				undoDel(mostRecent);
				break;
			case EDIT:
				undoEdit(mostRecent);
				break;
			case MARK:
			case UNMARK:
				undoStatus(mostRecent);
				break;
			case CLEAR:
				undoClear(mostRecent);
				break;
			default:
				return UNABLE_UNDO;
			}
			storage.writeDataToDisk(list.toVector());
			return STATUS_UNDO;
		}
	}

	//@author A0099943J
	private void undoClear(LogEntry mostRecent) {
		list = mostRecent.getTasks();
		recentChanges.clear();
		for (int i=0; i<list.getNumTask(); i++) {
			recentChanges.add(i);
		}
	}

	//@author A0099943J
	private void undoStatus(LogEntry mostRecent) {
		Vector<Task> markedTask = mostRecent.getTasks().toVector();
		recentChanges.clear();
		for (int i=0; i<markedTask.size(); i++) {
			int taskIndex = list.searchTask(markedTask.get(i));
			boolean previousStatus = markedTask.get(i).getStatus();
			list.setTaskStatus(taskIndex, previousStatus);
			recentChanges.add(taskIndex);
		}
	}

	//@author A0099943J
	private void undoEdit(LogEntry mostRecent) {
		Vector<Task>    editedTask      = mostRecent.getTasks().toVector();
		Vector<Integer> editedIndex = new Vector<Integer>();

		for (int i=0; i < editedTask.size(); i+=2) {
			Task current  = editedTask.get(i);
			int taskIndex = list.searchTask(current);
			editedIndex.add(taskIndex);
		}

		Collections.sort(editedIndex);
		recentChanges = new Vector<Integer>(editedIndex);

		for (int i=editedIndex.size()-1; i>=0; i--) {
			list.deleteTask(editedIndex.get(i));
		}
		for (int i=1; i < editedTask.size(); i+=2) {
			list.addTask(editedTask.get(i));
		}
	}

	//@author A0099943J
	private void undoDel(LogEntry mostRecent) {
		Vector<Task> deletedTask = mostRecent.getTasks().toVector();
		recentChanges                    = new Vector<Integer>();

		for (int i=0; i<deletedTask.size(); i++) {
			list.addTask(deletedTask.get(i));
		}
		for (int i=0; i<deletedTask.size(); i++) {
			Task current = deletedTask.get(i);
			recentChanges.add(list.searchTask(current));
		}
	}

	//@author A0099943J
	private void undoAdd(LogEntry mostRecent) {
		Task addedTask = mostRecent.getTasks().getTask(0);
		int addedTaskIndex = list.searchTask(addedTask);
		list.deleteTask(addedTaskIndex);
		recentChanges = new Vector<Integer>();
		recentChanges.add(addedTaskIndex);
		recentChanges.clear();
	}

	//@author A0099943J
	private String clearTask (Command newCommand) {
		// Add new entry to Log
		LogEntry entry = new LogEntry(DateTime.now(), "clear", list);
		log.add(entry);
		list = new TaskList();

		// Update database
		storage.updateLocalFiles(list.toVector(), log);

		return STATUS_CLEAR;
	}

	//@author A0099943J
	private String addTask(Command newCommand) {
		return addTask(newCommand,DISABLE_LOG);
	}

	//@author A0099943J
	private String addTask(Command newCommand,boolean disableLog) {
		String          	description = newCommand.getDescription();      
		Vector <DateTime> 	taskDT	= newCommand.getDateTime();
		// No description found
		if (description == null) {
			return INVALID_COMMAND;
		}

		// Start time is after end time
		if (taskDT.get(1).isBefore(taskDT.get(0))) {
			DateTime tempDT = new DateTime(taskDT.get(1));
			taskDT.set(1,taskDT.get(0));
			taskDT.set(0,tempDT);
		}

		// Task already expired
		if (taskDT.get(1).isBeforeNow()) {
			return EXPIRED_TASK;
		}

		// Task already exists in list
		Task newTask = new Task(description,taskDT.get(0),taskDT.get(1));
		if (list.searchTask(newTask) >= 0) {
			return DUPLICATE_TASK;
		}

		// Update list, data file and recent changes
		list.addTask(newTask);
		storage.updateLocalFiles(list.toVector(), log);

		recentChanges = new Vector<Integer>();
		recentChanges.add(list.searchTask(newTask));

		// Update log
		if (!disableLog) {
			TaskList temp = new TaskList();
			temp.addTask(new Task(newTask));
			LogEntry newEntry = new LogEntry(DateTime.now(), "add", temp);
			log.add(newEntry);
			storage.writeLogToDisk(log);
		}        

		return String.format(STATUS_ADD,description);
	}

	//@author A0099943J
	private Vector <Integer> removeDuplicate(Vector <Integer> indexList) 
			throws IndexOutOfBoundsException {
		boolean[] checkDuplicate = new boolean[list.getNumTask()];
		Arrays.fill(checkDuplicate, false);
		for (int i = indexList.size() - 1; i >=0; i--) {
			int taskIndex = indexList.get(i);
			if (!checkDuplicate[taskIndex]) {
				checkDuplicate[taskIndex] = true;
			} else {
				indexList.remove(i);
			}
		}
		Collections.sort(indexList);
		return indexList;
	}

	//@author A0099943J
	private String deleteTask(Command newCommand) {
		return deleteTask(newCommand,DISABLE_LOG);
	}

	//@author A0099943J
	private String deleteTask(Command newCommand, boolean logDisable) {
		String returnStatus = "Task ";
		Vector<Integer> deleteIndex = newCommand.getIndexList();
		recentChanges.clear();
		try {
			if (deleteIndex.size() == 0) {
				return NO_ARGUMENTS;
			} else {

				TaskList temp = new TaskList();

				deleteIndex = removeDuplicate(deleteIndex);
				for (int i = deleteIndex.size() - 1; i >= 0; i--) {
					int taskIndex = deleteIndex.get(i);
					returnStatus  = returnStatus.concat(Integer.toString(taskIndex + 1));
					returnStatus  = returnStatus.concat(i == 0 ? "" : ",");

					// logging each deleted task
					Task tempTask = new Task(list.getTask(taskIndex));
					temp.addTask(tempTask);

					// deletes Task
					list.deleteTask(taskIndex);
				}

				// add LogEntry
				if (!logDisable) {
					LogEntry newEntry = new LogEntry(DateTime.now(), "del", temp);
					log.add(newEntry);

					// Update database
					storage.updateLocalFiles(list.toVector(), log);
				}
				return String.format(STATUS_DELETE,returnStatus);
			}
		} catch (NullPointerException e1) {
			return INVALID_COMMAND;
		} catch (IndexOutOfBoundsException e2) {
			return INDEX_OUT_OF_BOUNDS;
		}
	}

	//@author A0099943J
	private String editTask(Command newCommand) {
		try {
			int  	editIndex 			= newCommand.getEditIndex();
			String  taskDescription     = newCommand.getDescription();
			Vector <DateTime> taskDT    = newCommand.getDateTime();

			if (editIndex == NO_EDIT_INDEX) {
				return NO_ARGUMENTS;
			}

			else try { 
				// instantiate empty TaskList for LogEntry object   
				TaskList temp = new TaskList();                  

				// Store original task
				Task original = new Task(list.getTask(editIndex));
				temp.addTask(original);
				Task modified = new Task(list.setTask(editIndex,taskDescription,taskDT.get(0),taskDT.get(1)));

				// Store edited task
				temp.addTask(modified);				
				log.add(new LogEntry (DateTime.now(), "edit", temp));

				// Update database
				storage.updateLocalFiles(list.toVector(), log);

			} catch (IndexOutOfBoundsException e) {
				return INDEX_OUT_OF_BOUNDS;
			}

			return String.format(STATUS_UPDATE,editIndex + 1);
		} catch (ArrayIndexOutOfBoundsException e){
			return NO_ARGUMENTS;
		}
	} 

	//@author A0099943J
	private String markTask(Command newCommand) {
		return setStatus(newCommand,true);
	}

	//@author A0099943J
	private String unmarkTask(Command newCommand) {
		return setStatus(newCommand,false);
	}

	//@author A0099943J
	private String setStatus(Command newCommand, boolean status) {
		String returnStatus = "Task ";
		Vector<Integer> updateIndex = newCommand.getIndexList();
		recentChanges.clear();
		try {
			if (updateIndex.size() == EMPTY_INDEX_LIST) {
				return NO_ARGUMENTS;
			}

			else {
				updateIndex = removeDuplicate(updateIndex);
				recentChanges = new Vector<Integer>(updateIndex);

				// Intialize log entry details
				String commandString = (status ? "mark" : "unmark");
				TaskList temp = new TaskList();

				for (int i = updateIndex.size() - 1; i >= 0; i--) {
					int taskIndex = updateIndex.get(i);
					returnStatus  = returnStatus.concat(Integer.toString(taskIndex +1));
					returnStatus  = returnStatus.concat(i == 0 ? "" : ",");

					// log Task marked/unmarked
					temp.addTask(new Task(list.getTask(taskIndex)));
					list.setTaskStatus(taskIndex,status);
				}

				// Add new entry to log
				LogEntry newEntry = new LogEntry(DateTime.now(), commandString, temp);
				log.add(newEntry);

				// Update database
				storage.updateLocalFiles(list.toVector(), log);

				return String.format(STATUS_COMPLETION, returnStatus, (status ? STATUS_DONE : STATUS_UNDONE));
			}
		} catch (NullPointerException e1) {
			return INVALID_COMMAND;
		} catch (IndexOutOfBoundsException e2) {
			return INDEX_OUT_OF_BOUNDS;
		}

	}
}